﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SwiftMVVM.ViewModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Collections;

namespace SwiftMVVM.ChangeTracking
{
    /// <summary>
    /// Monitors any object that implements 
    /// INotifyPropertyChanging and INotifyPropertyChanged for changes
    /// and maintains a list of undo/redo operations that can be used 
    /// to revert and redo the state of the object. The objects can 
    /// also expose collection properties that are trackable if they expose 
    /// INotifyCollectionChanging
    /// </summary>
    public sealed class ChangeManager : DependentBindingObject
    {
        /// <summary>
        /// Locking object used to synchronize access to the event args cache
        /// </summary>
        private static readonly object lockObject = new Object();

        /// <summary>
        /// In order to allow modifications after AcceptChanges has been called
        /// we provide the ability to suspend monitoring
        /// </summary>
        private bool suspendMonitoring;

        /// <summary>
        /// Flags that we're within the context of an Undo operation. 
        /// </summary>
        private bool inUndoOperation;

        /// <summary>
        /// Flags that we're within the context of an Redo operation. 
        /// </summary>
        private bool inRedoOperation;

        /// <summary>
        /// We use the NotifyChangeMonitor to monitor all changes to the object
        /// we're managing changes on. It will notify us when items are added/removed
        /// from collections as well as when properties change on given objects
        /// </summary>
        private NotifyChangeMonitor notifyChangeMonitor;

        /// <summary>
        /// We store the properties marked with the TrackChanges attribute by type. Once we examine
        /// the type once, there is no need to re-examine subsequent objects of the same type.
        /// This should speed up processing collections of the same object.
        /// The Dictionary stores the Type of the obect, the properties that are tracked, and
        /// whether or not the property is readonly. If a property is readonly we'll still track 
        /// it, but it will not show up in the Undo/Redo operation lists.
        /// </summary>
        private static readonly Dictionary<Type, Dictionary<string, bool>> typeTrackedProperties =
            new Dictionary<Type, Dictionary<string, bool>>();

        /// <summary>
        /// In order to track changes to collections we need to keep a list of what items
        /// have beeen deleted from the collection since the last time AcceptChanges was called.
        /// The dictionary stores each collection and the items that are in the Deleted state.
        /// We'll use this to re-add the deleted objects on an undo. Or do mark them as deleted in the 
        /// delta list
        /// </summary>
        private readonly Dictionary<INotifyCollectionChanged, HashSet<INotifyPropertyChanged>>
            deletedCollectionItems = new Dictionary<INotifyCollectionChanged, HashSet<INotifyPropertyChanged>>();

        /// <summary>
        /// In order to track changes to collections we need to keep a list of what items
        /// have beeen added to the collection since the last time AcceptChanges was called.
        /// The dictionary stores each collection and the items that are in the Added state.
        /// We'll use this to delete the added objects on an undo. Or do mark them as added in the 
        /// delta list
        /// </summary>
        private readonly Dictionary<INotifyCollectionChanged, HashSet<INotifyPropertyChanged>>
            addedCollectionItems = new Dictionary<INotifyCollectionChanged, HashSet<INotifyPropertyChanged>>();

        /// <summary>
        /// Because calculating the delta list can be expensive, we'll only calculate it once per haschanges
        /// </summary>
        private readonly ObservableCollection<ChangeDetail> currentDeltaList = new ObservableCollection<ChangeDetail>();

        /// <summary>
        /// This is essentially the heart of the ChangeManager.
        /// A linear, action by action list of changes since the last time
        /// accept changes was called. We use this to determine the Delta, Undo, Redo lists.
        /// We also use this to perform all Undo and Redo actions.
        /// </summary>
        private readonly List<ChangeDetail> changeList = new List<ChangeDetail>();

        /// <summary>
        /// When tracking DependentBindingObjects PropertyChanged events can end up cascading
        /// across many objects for 100's of changes. It makes no sense to fire a HasChanges
        /// on us for each change. Instead we'll fire HasChanges only when all cascading changes
        /// complete
        /// </summary>
        private KeyValuePair<object, string>? suppressHasChangesInfo;

        private int currentChangePosition;

        /// <summary>
        /// Our current position in the undo/redo chain. CurrentChangePosition
        /// is monitored by several other properties(Undo/Redo list etc). 
        /// </summary>
        private int CurrentChangePosition
        {
            get { return this.currentChangePosition; }
            set { SetPropertyValue(() => { }, ref this.currentChangePosition, value); }
        }

        /// <summary>
        /// Constructs a new instance of the ChangeManager and immediately begins monitoring
        /// the value passed in.
        /// </summary>
        /// <param name="value">Must implement both INotifyPropertyChanged and INotifyPropertyChanging</param>
        public ChangeManager(INotifyPropertyChanged value)
        {
            if (value == null) { throw new ArgumentNullException("value"); }

            //It's pretty much required that to property change track anything we 
            //need to be able to determine the value of a property before it actually changes.
            //by implementing INotifyPropertyChanging, we can query the object on a property change
            //and get all the info we need.
            if ((value as INotifyPropertyChanging) == null) { throw new InvalidOperationException("value must also implement INotifyPropertyChanging"); }

            //Create the change monitor and get going.
            this.notifyChangeMonitor = new NotifyChangeMonitor();
            this.notifyChangeMonitor.MonitoredObjectPropertyChangedNotificationsSent += new EventHandler<PropertyChangeEventArgs>(NotifyChangeMonitor_MonitoredObjectPropertyChangedNotificationsSent);
            this.notifyChangeMonitor.MonitoredCollectionChanged += new EventHandler<CollectionChangeEventArgs>(NotifyChangeMonitor_MonitoredCollectionChanged);
            this.notifyChangeMonitor.MonitoredObjectPropertyChanged += new EventHandler<PropertyChangeEventArgs>(NotifyChangeMonitor_MonitoredObjectPropertyChanged);
            this.notifyChangeMonitor.VerifyMonitorObjectProperty += new EventHandler<VerifyMonitorObjectPropertyEventArgs>(NotifyChangeMonitor_VerifyMonitorObjectProperty);
            this.notifyChangeMonitor.StartMonitor(value);

            //Set the current state of the object as the Accepted state.
            //Changes will all start from this point.
            this.AcceptChanges();
        }

        /// <summary>
        /// Connects the object passed in as value to change tracking
        /// This will recurse thru all properties on this object that support INotifyPropertyChanged
        /// If the object exposes properties implementing INotifyCollectionChanged, then they will 
        /// be iterated thru as well
        /// </summary>
        /// <param name="value">The object we want to monitor</param>
        /// <param name="emptyAction"></param>
        public void StartMonitor(INotifyPropertyChanged value, Action emptyAction)
        {
            //Slight speed penalty here, but it prevents us from getting the 
            //wrong property name (maintainability > speed here)
            var methodName = emptyAction.Method.Name;
            var propertyName = methodName.Substring(5, methodName.LastIndexOf('>') - 5);
            StartMonitor(value, propertyName);
        }

        /// <summary>
        /// Connects the object passed in as value to change tracking
        /// This will recurse thru all properties on this object that support INotifyPropertyChanged
        /// If the object exposes properties implementing INotifyCollectionChanged, then they will 
        /// be iterated thru as well
        /// </summary>
        /// <param name="value">The object we want to monitor</param>
        /// <param name="propertyName">The name of the property</param>
        public void StartMonitor(INotifyPropertyChanged value, string propertyName)
        {
            if (string.IsNullOrWhiteSpace(propertyName)) { throw new ArgumentNullException("propertyName"); }
            this.notifyChangeMonitor.StartMonitor(value, propertyName);
        }


        /// <summary>
        /// NotifyChangeMonitor is going to call us to determine what properties we're 
        /// interested in monitoring. We only want to monitor properties marked with the
        /// TrackChanges attribute. However, we also want the properties to implement
        /// BOTH INotifyPropertyChanged and INotifyPropertyChanging. Unless they're 
        /// a colleciton. In that case we don't care.
        /// </summary>
        /// <param name="sender">NotifyChangeMonitor</param>
        /// <param name="e">All the information we need to know about the property</param>
        internal static void NotifyChangeMonitor_VerifyMonitorObjectProperty(object sender, VerifyMonitorObjectPropertyEventArgs e)
        {
            //Check to see if we've already examined this type for properties that are marked 
            //with TrackChanges and implement the interfaces we require.
            var trackedPropertiesByType = typeTrackedProperties.GetOrAdd(lockObject, e.SourceObject.GetType(), key =>
            {
                Dictionary<string, bool> trackedProperties = new Dictionary<string,bool>();
                foreach (var propertyInfo in e.SourceObject.GetType().GetProperties()
                    .Where(propertyInfo => propertyInfo.GetCustomAttributes(typeof(TrackChangesAttribute), true).Any()))
                {
                    //Ok the property is marked as TrackChanges. If the property implements INotifyPropertyChanged
                    //then we have the additional requirement that it also implement INotifyPropertyChanging
                    //UNLESS it implements INotifyCollectionChanged. In that case we'll track it's additions
                    //and deletions and changes on it's items. But we won't track
                    //individual properties on the collection unless it also implements INotifyPropertyChanging
                    if (typeof(INotifyPropertyChanged).IsAssignableFrom(propertyInfo.PropertyType))
                    {
                        if (typeof(INotifyCollectionChanged).IsAssignableFrom(propertyInfo.PropertyType))
                        {
                            //If the object implements INotifyCollectionChanged, we also will need to perform iteration
                            //operations so make sure it supports IList
                            if (!typeof(IList).IsAssignableFrom(propertyInfo.PropertyType))
                            {
                                throw new InvalidOperationException("Collection objects that implement INotifyCollectionChanged must also implement IList");
                            }
                        }
                        else
                        {
                            if (!typeof(INotifyPropertyChanging).IsAssignableFrom(propertyInfo.PropertyType))
                            {
                                throw new InvalidOperationException("Non collection(INotifyCollectionChanged) objects that implement INotifyPropertyChanged must also implement INotifyPropertyChanging");
                            }
                        }
                    }

                    trackedProperties.Add(propertyInfo.Name, propertyInfo.CanWrite);
                }

                return trackedProperties;
            });

            //Return whether or not we're tracking the property on this type by setting the
            //Cancel arg to true if we're not going to track this property
            e.Cancel = trackedPropertiesByType == null ||
                !typeTrackedProperties[e.PropertyInfo.ReflectedType].ContainsKey(e.PropertyInfo.Name);
        }

        /// <summary>
        /// What we're doing here is firing off a HasChanges when the first property that initiated
        /// a cascading change returns
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void NotifyChangeMonitor_MonitoredObjectPropertyChangedNotificationsSent(object sender, PropertyChangeEventArgs e)
        {
            //We may want to temporarily suspend change notifications. This would happen
            //if we were loading a sub collection from a thread etc. We wouldnt want the new
            //items showing up as 'New'
            if (suspendMonitoring) { return; }

            if (e.SourceObject is DependentBindingObject &&
                suppressHasChangesInfo != null &&
                suppressHasChangesInfo.Value.Key == e.SourceObject &&
                suppressHasChangesInfo.Value.Value == e.PropertyName)
            {
                suppressHasChangesInfo = null;
                OnPropertyChanging("HasChanges");
                OnPropertyChanged("HasChanges");
            }
        }



        /// <summary>
        /// We only support PropertyChanged notifications on objects that support both INotifyPropertyChanged
        /// and INotifyPropertyChanging (as if the comments haven't already drilled that in). 
        /// When the property changes we add it to our list of changes to the object and increment 
        /// the current change pointer. 
        /// </summary>
        /// <param name="sender">The NotifyChangeMonitor</param>
        /// <param name="e">Information about the object that changed</param>
        private void NotifyChangeMonitor_MonitoredObjectPropertyChanged(object sender, PropertyChangeEventArgs e)
        {
            //We may want to temporarily suspend change notifications. This would happen
            //if we were loading a sub collection from a thread etc. We wouldnt want the new
            //items showing up as 'New'
            if (suspendMonitoring) { return; }

            if (e.SourceObject is DependentBindingObject &&
                suppressHasChangesInfo == null)
            {
                suppressHasChangesInfo = new KeyValuePair<object, string>(e.SourceObject, e.PropertyName);
            }

            //Since we require both the old and new values to create a Change record
            //the object must implement INotifyPropertyChanging so we can get the prior value 
            //of the property. That said, we may be monitoring a collection INotifyCollectionChanged
            //that only implements INotifyPropertyChanged. In that case we do not track anything 
            //here for a collection.
            if (e.SourceObject is INotifyPropertyChanging)
            {
                bool isTrackedProperty;
                Dictionary<string, bool> objectTrackedProperties;

                //Only record the change if we're not in an undo or redo.
                //Otherwise the change has already been recorded
                if (!inUndoOperation && !inRedoOperation &&
                    typeTrackedProperties.TryGetValue(e.SourceObject.GetType(), out objectTrackedProperties) &&
                    objectTrackedProperties.TryGetValue(e.PropertyName, out isTrackedProperty))
                {
                    RemoveRedoChanges();

                    changeList.Add(new ChangeDetail(
                        e.SourceObject, e.PropertyName, e.NewValue, e.OldValue, !isTrackedProperty));

                    //Any new change requires us to position the Current marker to the last change
                    CurrentChangePosition = changeList.Count - 1;
                }
            }
        }

        /// <summary>
        /// Fires when an item is added or removed from a Monitored collection
        /// </summary>
        /// <param name="sender">The NotifyChangeMonitor</param>
        /// <param name="e">Information about the changed collection and the items that changed</param>
        private void NotifyChangeMonitor_MonitoredCollectionChanged(object sender, CollectionChangeEventArgs e)
        {
            //We may want to temporarily suspend change notifications. This would happen
            //if we were loading a sub collection from a thread etc. We wouldnt want the new
            //items showing up as 'New'
            if (suspendMonitoring) { return; }

            //Here we maintain a list of items that have been added or removed
            //since the last call to AcceptChanges
            var collection = (ICollection)e.SourceCollection;
            HashSet<INotifyPropertyChanged> deletedItems;
            HashSet<INotifyPropertyChanged> addedItems;
            if (!deletedCollectionItems.TryGetValue(e.SourceCollection, out deletedItems))
            {
                deletedCollectionItems.Add(e.SourceCollection, deletedItems = new HashSet<INotifyPropertyChanged>());
            }
            if (!addedCollectionItems.TryGetValue(e.SourceCollection, out addedItems))
            {
                addedCollectionItems.Add(e.SourceCollection, addedItems = new HashSet<INotifyPropertyChanged>());
            }

            switch (e.NotifyCollectionChangedEventArgs.Action)
            {
                case NotifyCollectionChangedAction.Move:
                    break;
                case NotifyCollectionChangedAction.Remove:
                    {
                        //Iterate thru each removed item. If it was an item original to the collection,
                        //in other words it existed when AcceptChanges was called, then move it
                        //to the deleted list. On the other hand if it was new, then just remove it.
                        foreach (INotifyPropertyChanged item in e.NotifyCollectionChangedEventArgs.OldItems)
                        {
                            if (!inUndoOperation && !this.inRedoOperation)
                            {
                                RemoveRedoChanges();

                                changeList.Add(new ChangeDetail(
                                    (INotifyPropertyChanged)collection,
                                    notifyChangeMonitor.GetMonitoredObjectName((INotifyPropertyChanged)collection),
                                    item, "Removed", false));

                                CurrentChangePosition = changeList.Count - 1;
                            }

                            if (!addedItems.Contains(item))
                            {
                                deletedItems.Add(item);
                            }

                            addedItems.Remove(item);
                        }
                        break;
                    }
                case NotifyCollectionChangedAction.Add:
                    {
                        //Iterate thru each added item. If it was an item original to the collection,
                        //in other words it existed when AcceptChanges was called, then move it
                        //to the added list. On the other hand if it was deleted, then just remove it.
                        foreach (var item in e.NotifyCollectionChangedEventArgs.NewItems)
                        {
                            if (!inUndoOperation && !this.inRedoOperation)
                            {
                                RemoveRedoChanges();

                                changeList.Add(new ChangeDetail(
                                    (INotifyPropertyChanged)collection,
                                    notifyChangeMonitor.GetMonitoredObjectName((INotifyPropertyChanged)collection),
                                    item, "Added", false));

                                CurrentChangePosition = changeList.Count - 1;
                            }

                            var notifyPropertyChanged = (INotifyPropertyChanged)item;

                            if (!deletedItems.Contains(notifyPropertyChanged))
                            {
                                addedItems.Add(notifyPropertyChanged);
                            }

                            deletedItems.Remove(notifyPropertyChanged);
                        }
                        break;
                    }
                default:
                    throw new NotImplementedException();
            }
            //Fired off a HasChanges so listeners can be made aware that a change has occured
            OnPropertyChanging("HasChanges");
            OnPropertyChanged("HasChanges");
        }



        /// <summary>
        /// Marks the current state of the object as Accepted.
        /// What this means is that the object no longer has Changes. 
        /// This should be called after any "Save" type operation.
        /// </summary>
        public void AcceptChanges()
        {
            this.changeList.Clear();
            this.CurrentChangePosition = -1;
            this.addedCollectionItems.Clear();
            this.deletedCollectionItems.Clear();
            this.currentDeltaList.Clear();
        }



        /// <summary>
        /// Returns a list of undo items. Undo items are defined as anything
        /// to the 'left' of the current change position. Since we will only
        /// be iterating over the list I've implemented this as just an iterator.
        /// No need to create/maintain another list for this.
        /// </summary>
        [DependsOn("HasChanges")]
        private IEnumerable<ChangeDetail> UndoItems
        {
            get { for (int i = 0; i <= this.CurrentChangePosition; i++) { yield return this.changeList[i]; } }
        }

        /// <summary>
        /// Returns a list of Changes that the user can actually undo.
        /// Readonly changes are 'consequence changes', in other words calculated fields that will
        /// revert with their associated change. Hence the reason we do not include them in this list.
        /// As with the UndoItems, we implement this as an Iterator because again there is no need
        /// to create a new data structure to hand this back to the caller.
        /// </summary>
        [DependsOn("UndoItems")]
        public IEnumerable<string> UndoList
        {
            get
            {
                //Iterate over writable changes in reverse
                foreach (var item in UndoItems.Where(item => { return !item.IsReadOnly; }).Reverse())
                {
                    if (item.ObjectThatChanged is ICollection)
                    {
                        //We write out collection changes with descriptions of the Add or 
                        //Remove instead of the property name that changed
                        yield return string.Format("{0} {1}", item.PriorValue, ResolveDescription(item.Value.GetType()));
                    }
                    else
                    {
                        yield return string.Format("Changed {0}.{1}",
                            ResolveDescription(item.ObjectThatChanged.GetType()),
                            ResolveDescription(item.ObjectThatChanged.GetType(), item.Property));
                    }
                }
            }
        }

        /// <summary>
        /// Allow the user to put in a 'Friendly' description on
        /// any object or Property via the Description attribute
        /// </summary>
        /// <param name="type"></param>
        /// <param name="propertyName"></param>
        /// <returns></returns>
        private static string ResolveDescription(Type type, string propertyName = null)
        {
            var descriptionAttributes =
                (DescriptionAttribute[])(string.IsNullOrWhiteSpace(propertyName) ?
                type.GetCustomAttributes(typeof(DescriptionAttribute), true) :
                type.GetProperty(propertyName).GetCustomAttributes(typeof(DescriptionAttribute), true));

            return descriptionAttributes.Count() == 1 ?
                descriptionAttributes.First().Description :
                propertyName ?? type.Name;
        }


        /// <summary>
        /// Returns a list of Changes that the user has previously Undone.
        /// Redoable changes are essentially defined as any change to the 'right' of the current change position.
        /// Readonly changes are 'consequence changes', in other words calculated fields that will
        /// revert with their associated change. Hence the reason we do not include them in this list.
        /// As with the UndoItems, we implement this as an Iterator because again there is no need
        /// to create a new data structure to hand this back to the caller.
        /// </summary>
        [DependsOn("HasChanges")]
        public IEnumerable<string> RedoList
        {
            get
            {
                for (int i = this.CurrentChangePosition + 1; i < this.changeList.Count; i++)
                {
                    var item = this.changeList[i];
                    if (!item.IsReadOnly)
                    {
                        if (item.ObjectThatChanged is ICollection)
                        {
                            //We write out collection changes with descriptions of the Add or 
                            //Remove instead of the property name that changed
                            yield return string.Format("{0} {1}", item.PriorValue, ResolveDescription(item.Value.GetType()));
                        }
                        else
                        {
                            yield return string.Format("Changed {0}.{1}",
                                ResolveDescription(item.ObjectThatChanged.GetType()),
                                ResolveDescription(item.ObjectThatChanged.GetType(), item.Property));
                        }
                    }
                }
            }
        }

        /// <summary>
        /// We're overriding OnPropertyChanged for two reasons.
        /// For performance reasons we want to suppress HasChanges for long dependency
        /// chains so that we fire a HasChanges when the dependency batch completes.
        /// Secondly calculating the DeltaList is expensive. So rather than 
        /// calculating it when it's asked for. Calculate it when the Mode or UndoItems
        /// list changes. Cache it and return that.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="propertyName"></param>
        protected override void OnPropertyChanged(string propertyName)
        {
            if (suppressHasChangesInfo != null && propertyName == "HasChanges")
            {
                return;
            }

            if ((inRedoOperation || inUndoOperation) && propertyName == "HasChanges")
            {
                return;
            }

            if (propertyName == "UndoItems" ||
                propertyName == "DeltaMode")
            {
                currentDeltaList.Clear();
                foreach (var item in CalculateDeltaList())
                {
                    currentDeltaList.Add(item);
                }
            }

            base.OnPropertyChanged(propertyName);
        }


        /// <summary>
        /// Temporarily suspend monitoring operations
        /// </summary>
        /// <param name="action"></param>
        public void SuspendMonitoring(Action action)
        {
            if (action == null) { throw new ArgumentNullException("action"); }

            var thread = System.Threading.Thread.CurrentThread;
            if (thread != null && (thread.IsThreadPoolThread || thread.IsBackground))
            {
                throw new InvalidOperationException("SuspendMonitoring can only be invoked from the UI thread");
            }

            try
            {
                this.suspendMonitoring = true;
                action();
            }
            finally
            {
                this.suspendMonitoring = false;
            }
        }



        /// <summary>
        /// This will generate a list of actual changes to the object. 
        /// For example, if an item is new, we only care that it's new. We don't care that it changed
        /// because it's in the 'New' state, same applies to Deleted items. We do however want to know
        /// changes associated with 'Accepted' items. In other words items that were in the list the last time
        /// AcceptChanges was called. Also if multiple changes were made to a property we only
        /// return the original and last state of the object. We're not interested in the intermediate changes
        /// to a property in this list 
        /// </summary>
        private IEnumerable<ChangeDetail> CalculateDeltaList()
        {
            //Collection of items we will potentially return 
            var collection = new Collection<ChangeDetail>();

            //We'll use this dictionary to track multiple changes to the object.
            //Essentially we'll loop thru the Undo items in reverse.
            //We'll store the first Current and Prior value we see.
            //If we see this object again in the change list we'll update the prior value
            //in our lookup. Once we're done going thru the list we'll have the current and
            //original accepted value for every item in our change list
            var changeLookup = new Dictionary<INotifyPropertyChanged, Dictionary<string, ChangeDetail>>();
            var newAndDeletedCollectionItems = new HashSet<INotifyPropertyChanged>();
            foreach (var item in this.UndoItems.Reverse())
            {
                Dictionary<string, ChangeDetail> differentials;
                if (!changeLookup.TryGetValue(item.ObjectThatChanged, out differentials))
                {
                    changeLookup.Add(item.ObjectThatChanged, differentials = new Dictionary<string, ChangeDetail>());
                }

                var objectThatChangedAsNotifyCollection = item.ObjectThatChanged as INotifyCollectionChanged;
                if (objectThatChangedAsNotifyCollection != null)
                {
                    //If the item is a collection item, we need to determine if the changed
                    //row is new or deleted.
                    HashSet<INotifyPropertyChanged> deletedItems;
                    deletedCollectionItems.TryGetValue(objectThatChangedAsNotifyCollection, out deletedItems);

                    HashSet<INotifyPropertyChanged> newItems;
                    addedCollectionItems.TryGetValue(objectThatChangedAsNotifyCollection, out newItems);

                    //If it's in either the new or deleted item list, add it to the 
                    //nonAcceptedCollectionItems list
                    if ((deletedItems != null && deletedItems.Contains((INotifyPropertyChanged)item.Value)) ||
                        (newItems != null && newItems.Contains((INotifyPropertyChanged)item.Value)))
                    {
                        collection.Add(new ChangeDetail(item));

                        //Basically we want to earmark rows that are in the new
                        //or deleted state. Reason being is that we do not want
                        //to add property changes on new and deleted items to our
                        //change list
                        var notifyPropertyChanged = (INotifyPropertyChanged)item.Value;
                        if (!newAndDeletedCollectionItems.Contains(notifyPropertyChanged))
                        {
                            newAndDeletedCollectionItems.Add(notifyPropertyChanged);
                        }
                    }
                }
                else
                {
                    ChangeDetail differential;
                    if (differentials.TryGetValue(item.Property, out differential))
                    {
                        //Update the prior value
                        differential.PriorValue = item.PriorValue;
                    }
                    else
                    {
                        differentials.Add(item.Property, differential = new ChangeDetail(item));
                        collection.Add(differential);
                    }
                }
            }

            //Return the changes, again reversed so they can see the changes in the order that
            //they were made.
          
            //Return added and deleted rows, and items with properties that have changed.
            //What we do not care about, however, is items that are new or deleted that
            //have changed. No need to log changes to a deleted row, or a new one.
            //The user may have made several edits but the first and last edit
            //end up being the same. If that's the case it's not ultimately a change
            return collection.Reverse()
                .Where(changeDetail => 
                    !newAndDeletedCollectionItems.Contains(changeDetail.ObjectThatChanged) &&
                    NotifyProxyTypeManager.Compare(changeDetail.PriorValue, changeDetail.Value));
            
        }

        /// <summary>
        /// This is a list of actual changes to the object. 
        /// For example, if an item is new, we only care that it's new. We don't care that it changed
        /// because it's in the 'New' state, same applies to Deleted items. We do however want to know
        /// changes associated with 'Accepted' items. In other words items that were in the list the last time
        /// AcceptChanges was called. Also if multiple changes were made to a property we only
        /// return the original and last state of the object. We're not interested in the intermediate changes
        /// to a property in this list
        /// </summary>
        [DependsOn("UndoItems")]
        [DependsOn("DeltaMode")]
        public IEnumerable<ChangeDetail> DeltaList
        { get { return currentDeltaList; } }

        /// <summary>
        /// Returns whether or not the ChangeManager
        /// has detected any changes to the object graph it is monitoring
        /// </summary>
        [DependsOn("CurrentChangePosition")]
        public bool HasChanges
        {
            get { return changeList.Count > 0 && CurrentChangePosition >= 0; }
        }

        /// <summary>
        /// When a user takes a new action all of the changes on the redo side of the list
        /// must be removed
        /// </summary>
        private void RemoveRedoChanges()
        {
            //Remove all available "Redo" changes
            for (int i = changeList.Count - 1; i > CurrentChangePosition; i--) { changeList.RemoveAt(i); }

            CurrentChangePosition = changeList.Count - 1;
        }

        /// <summary>
        /// Performs an actual Undo operation, reverting the last change
        /// made to the object graph.
        /// </summary>
        /// <param name="count">The number of changes to undo, this must not exceed the UndoList.Count</param>
        public void Undo(int count = 1)
        {
            if (count > this.UndoList.Count()) 
            {
                //throw new InvalidOperationException("Undo count exceeds available undo operations in the Undo list. Please check HasChanges, or UndoList.Count prior to calling Undo"); 
                count = this.UndoList.Count();
            }
            if (CurrentChangePosition != -1)
            {
                int changesApplied = 0;
                while (changesApplied < count)
                {
                    try
                    {
                        inUndoOperation = true;

                        //The first time we revert we will not have our current state in
                        //the queue. Make sure to add it.

                        var objectChange = this.changeList[CurrentChangePosition];

                        CurrentChangePosition--;

                        if (objectChange != null && !objectChange.IsReadOnly)
                        {
                            changesApplied++;
                            ApplyObjectChange(objectChange, true);
                        }
                    }
                    finally
                    {
                        inUndoOperation = false;
                    }
                }
                if (changesApplied > 0)
                {
                    OnPropertyChanging("CurrentChangePosition");
                    OnPropertyChanged("CurrentChangePosition");
                }
            }
        }

        /// <summary>
        /// Performs an actual Redo operation, reapplying the last change
        /// made to the object graph.
        /// </summary>
        /// <param name="count">The number of changes to redo, this must not exceed the RedoList.Count</param>
        public void Redo(int count = 1)
        {
            if (count > this.RedoList.Count()) 
            {
                //throw new InvalidOperationException("Redo count exceeds available redo operations in the Redo list. Please check HasChanges, or RedoList.Count prior to calling Redo");
                count = this.RedoList.Count();
            }

            int changesApplied = 0;
            //Since we have readonly changes we have to 
            //run thru them sequentially till we either hit the next 
            //writable change, or the end of the change list
            while (true)
            {
                if (changesApplied == count)
                {
                    //If we've reached the end of the change list stop.
                    if (CurrentChangePosition >= this.changeList.Count - 1)
                    {
                        break;
                    }

                    //If we have reached the next writable change, stop
                    var nextObjectChange = this.changeList[CurrentChangePosition + 1];
                    if (!nextObjectChange.IsReadOnly)
                    {
                        break;
                    }
                }

                try
                {
                    this.inRedoOperation = true;
                    CurrentChangePosition++;
                    var objectChange = this.changeList[CurrentChangePosition];
                    if (objectChange != null && !objectChange.IsReadOnly)
                    {
                        changesApplied++;
                        //Don't ignore propertychanges here, this must 
                        //be interpreted as if the user did it.
                        ApplyObjectChange(objectChange, false);
                    }
                }
                finally
                {
                    this.inRedoOperation = false;
                }
            }
            if (changesApplied > 0)
            {
                OnPropertyChanging("CurrentChangePosition");
                OnPropertyChanged("CurrentChangePosition");
            }
        }

        /// <summary>
        /// Applies the ChangeDetail to the object specified in ObjectThatChanged.
        /// </summary>
        /// <param name="changeDetail">The detail of the change</param>
        /// <param name="undo">Whether or not this is an Undo or Redo</param>
        private void ApplyObjectChange(ChangeDetail changeDetail, bool undo)
        {
            if (changeDetail == null) { throw new ArgumentNullException("objectChange"); }

            if ((changeDetail.ObjectThatChanged is INotifyCollectionChanged) &&
                            (((string)changeDetail.PriorValue) == "Added" ||
                            ((string)changeDetail.PriorValue) == "Removed"))
            {
                var list = (IList)changeDetail.ObjectThatChanged;
                if (((string)changeDetail.PriorValue) == "Added")
                {
                    if (undo)
                    {
                        list.Remove(changeDetail.Value);
                    }
                    else
                    {
                        list.Add(changeDetail.Value);
                    }

                }
                if (((string)changeDetail.PriorValue) == "Removed")
                {
                    if (undo)
                    {
                        list.Add(changeDetail.Value);
                    }
                    else
                    {
                        list.Remove(changeDetail.Value);
                    }
                }
            }
            else
            {
                var propertyInfo = changeDetail.ObjectThatChanged.GetType().
                    GetProperty(changeDetail.Property);

                propertyInfo.SetValue(changeDetail.ObjectThatChanged,
                    undo ? changeDetail.PriorValue : changeDetail.Value, null);
            }
        }
    }
}
